/*-------------------------------------------------------------------------
 *
 * toast_compression.c
 *	  用于 toast 压缩的函数。
 *
 * Copyright (c) 2021-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/common/toast_compression.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#ifdef USE_LZ4
#include <lz4.h>
#endif

#include "access/detoast.h"
#include "access/toast_compression.h"
#include "common/pg_lzcompress.h"
#include "fmgr.h"
#include "utils/builtins.h"

/* GUC */
int			default_toast_compression = TOAST_PGLZ_COMPRESSION;

#define NO_LZ4_SUPPORT() \
	ereport(ERROR, \
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
			 errmsg("compression method lz4 not supported"), \
			 errdetail("This functionality requires the server to be built with lz4 support.")))

/*
 * 使用 PGLZ 压缩一个 varlena。
 *
 * 返回压缩后的 varlena，如果压缩失败则返回 NULL。
 */
struct varlena * pglz_compress_datum(const struct varlena *fc_value)
{
	int32		fc_valsize,
				fc_len;
	struct varlena *fc_tmp = NULL;

	fc_valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(fc_value));

	/*
	 * 如果值的大小超出了压缩的允许范围，就没有必要浪费 palloc 周期。
	 */
	if (fc_valsize < PGLZ_strategy_default->min_input_size ||
		fc_valsize > PGLZ_strategy_default->max_input_size)
		return NULL;

	/*
	 * 计算 pglz 输出的最大可能大小，添加 varlena 开销所需的字节，并分配该数量的内存。
	 */
	fc_tmp = (struct varlena *) palloc(PGLZ_MAX_OUTPUT(fc_valsize) +
									VARHDRSZ_COMPRESSED);

	fc_len = pglz_compress(VARDATA_ANY(fc_value),
						fc_valsize,
						(char *) fc_tmp + VARHDRSZ_COMPRESSED,
						NULL);
	if (fc_len < 0)
	{
		pfree(fc_tmp);
		return NULL;
	}

	SET_VARSIZE_COMPRESSED(fc_tmp, fc_len + VARHDRSZ_COMPRESSED);

	return fc_tmp;
}

/*
 * 解压一个使用 PGLZ 压缩的 varlena。
 */
struct varlena * pglz_decompress_datum(const struct varlena *fc_value)
{
	struct varlena *fc_result;
	int32		fc_rawsize;

	/* 分配解压数据的内存 */
	fc_result = (struct varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(fc_value) + VARHDRSZ);

	/* 解压数据 */
	fc_rawsize = pglz_decompress((char *) fc_value + VARHDRSZ_COMPRESSED,
							  VARSIZE(fc_value) - VARHDRSZ_COMPRESSED,
							  VARDATA(fc_result),
							  VARDATA_COMPRESSED_GET_EXTSIZE(fc_value), true);
	if (fc_rawsize < 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg_internal("compressed pglz data is corrupt")));

	SET_VARSIZE(fc_result, fc_rawsize + VARHDRSZ);

	return fc_result;
}

/*
 * 解压部分使用 PGLZ 压缩的 varlena。
 */
struct varlena * pglz_decompress_datum_slice(const struct varlena *fc_value,
							int32 fc_slicelength)
{
	struct varlena *fc_result;
	int32		fc_rawsize;

	/* 分配解压数据的内存 */
	fc_result = (struct varlena *) palloc(fc_slicelength + VARHDRSZ);

	/* 解压数据 */
	fc_rawsize = pglz_decompress((char *) fc_value + VARHDRSZ_COMPRESSED,
							  VARSIZE(fc_value) - VARHDRSZ_COMPRESSED,
							  VARDATA(fc_result),
							  fc_slicelength, false);
	if (fc_rawsize < 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg_internal("compressed pglz data is corrupt")));

	SET_VARSIZE(fc_result, fc_rawsize + VARHDRSZ);

	return fc_result;
}

/*
 * 使用 LZ4 压缩一个 varlena。
 *
 * 返回压缩后的 varlena，如果压缩失败则返回 NULL。
 */
struct varlena * lz4_compress_datum(const struct varlena *fc_value)
{
#ifndef USE_LZ4
	NO_LZ4_SUPPORT();
	return NULL;				/* 保持编译器安静 */
#else
	int32		fc_valsize;
	int32		fc_len;
	int32		fc_max_size;
	struct varlena *fc_tmp = NULL;

	fc_valsize = VARSIZE_ANY_EXHDR(fc_value);

	/*
	 * 计算 LZ4 输出的最大可能大小，添加 varlena 开销所需的字节，并分配该数量的内存。
	 */
	fc_max_size = LZ4_compressBound(fc_valsize);
	fc_tmp = (struct varlena *) palloc(fc_max_size + VARHDRSZ_COMPRESSED);

	fc_len = LZ4_compress_default(VARDATA_ANY(fc_value),
							   (char *) fc_tmp + VARHDRSZ_COMPRESSED,
							   fc_valsize, fc_max_size);
	if (fc_len <= 0)
		elog(ERROR, "lz4 compression failed");

	/* 数据无法压缩，因此只需释放内存并返回 NULL */
	if (fc_len > fc_valsize)
	{
		pfree(fc_tmp);
		return NULL;
	}

	SET_VARSIZE_COMPRESSED(fc_tmp, fc_len + VARHDRSZ_COMPRESSED);

	return fc_tmp;
#endif
}

/*
 * 解压一个使用 LZ4 压缩的 varlena。
 */
struct varlena * lz4_decompress_datum(const struct varlena *fc_value)
{
#ifndef USE_LZ4
	NO_LZ4_SUPPORT();
	return NULL;				/* 保持编译器安静 */
#else
	int32		fc_rawsize;
	struct varlena *fc_result;

	/* 分配解压数据的内存 */
	fc_result = (struct varlena *) palloc(VARDATA_COMPRESSED_GET_EXTSIZE(fc_value) + VARHDRSZ);

	/* 解压数据 */
	fc_rawsize = LZ4_decompress_safe((char *) fc_value + VARHDRSZ_COMPRESSED,
								  VARDATA(fc_result),
								  VARSIZE(fc_value) - VARHDRSZ_COMPRESSED,
								  VARDATA_COMPRESSED_GET_EXTSIZE(fc_value));
	if (fc_rawsize < 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg_internal("compressed lz4 data is corrupt")));


	SET_VARSIZE(fc_result, fc_rawsize + VARHDRSZ);

	return fc_result;
#endif
}

/*
 * 解压部分使用 LZ4 压缩的 varlena。
 */
struct varlena * lz4_decompress_datum_slice(const struct varlena *fc_value, int32 fc_slicelength)
{
#ifndef USE_LZ4
	NO_LZ4_SUPPORT();
	return NULL;				/* 保持编译器安静 */
#else
	int32		fc_rawsize;
	struct varlena *fc_result;

	/* 切片解压在 1.8.3 之前不支持 */
	if (LZ4_versionNumber() < 10803)
		return lz4_decompress_datum(fc_value);

	/* 分配解压数据的内存 */
	fc_result = (struct varlena *) palloc(fc_slicelength + VARHDRSZ);

	/* 解压数据 */
	fc_rawsize = LZ4_decompress_safe_partial((char *) fc_value + VARHDRSZ_COMPRESSED,
										  VARDATA(fc_result),
										  VARSIZE(fc_value) - VARHDRSZ_COMPRESSED,
										  fc_slicelength,
										  fc_slicelength);
	if (fc_rawsize < 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg_internal("compressed lz4 data is corrupt")));

	SET_VARSIZE(fc_result, fc_rawsize + VARHDRSZ);

	return fc_result;
#endif
}

/*
 * 从 varlena 中提取压缩 ID。
 *
 * 如果 varlena 没有被压缩，则返回 TOAST_INVALID_COMPRESSION_ID。
 */
ToastCompressionId
toast_get_compression_id(struct varlena *fc_attr)
{
	ToastCompressionId fc_cmid = TOAST_INVALID_COMPRESSION_ID;

	/*
	 * 如果它是外部存储的，则从外部 toast 指针中获取压缩方法 ID。 如果是内联压缩，则从 toast 压缩头中获取。
	 */
	if (VARATT_IS_EXTERNAL_ONDISK(fc_attr))
	{
		struct varatt_external fc_toast_pointer;

		VARATT_EXTERNAL_GET_POINTER(fc_toast_pointer, fc_attr);

		if (VARATT_EXTERNAL_IS_COMPRESSED(fc_toast_pointer))
			fc_cmid = VARATT_EXTERNAL_GET_COMPRESS_METHOD(fc_toast_pointer);
	}
	else if (VARATT_IS_COMPRESSED(fc_attr))
		fc_cmid = VARDATA_COMPRESSED_GET_COMPRESS_METHOD(fc_attr);

	return fc_cmid;
}

/*
 * CompressionNameToMethod - 从压缩名称获取压缩方法
 *
 * 在可用的内置方法中搜索。如果在内置方法中未找到压缩，则返回 InvalidCompressionMethod。
 */
char CompressionNameToMethod(const char *fc_compression)
{
	if (strcmp(fc_compression, "pglz") == 0)
		return TOAST_PGLZ_COMPRESSION;
	else if (strcmp(fc_compression, "lz4") == 0)
	{
#ifndef USE_LZ4
		NO_LZ4_SUPPORT();
#endif
		return TOAST_LZ4_COMPRESSION;
	}

	return InvalidCompressionMethod;
}

/*
 * GetCompressionMethodName - 获取压缩方法名称
 */
const char * GetCompressionMethodName(char fc_method)
{
	switch (fc_method)
	{
		case TOAST_PGLZ_COMPRESSION:
			return "pglz";
		case TOAST_LZ4_COMPRESSION:
			return "lz4";
		default:
			elog(ERROR, "invalid compression method %c", fc_method);
			return NULL;		/* 保持编译器安静 */
	}
}
